import [Value,Symbol,Number,vList,Errors,Character,lString]
import structs/ArrayList
import text/EscapeSequence

LispParser : class { // TODO: improve this parsing hell
    text : String
    currIndex : UInt = 0
    
    init : func(=text)

    work : func() -> ArrayList<Value> {
        ret := ArrayList<Value> new()
        
        while(currIndex < text size) {
            val := read()
            if(val != null) {
                ret add(val)
            }
        }
        ret
    }
    
    read : func -> Value {
        skipWhite()
        
        next : Char
        if(currIndex < text size) {
            next = text[currIndex]
        }
    
        if(next == '\0') {
            currIndex = text size + 1
        }
        
        if(next) { // Check character
            if(next >= '0' && next <= '9') { // Hey a number!
                return readNumber() // Read it ;) 
            } else if(next == '-' && currIndex+1 < text size) { // A minus??
                nNext := text[currIndex+1]
                if(nNext >= '0' && nNext <= '9') { // Is it a negative number ?
                    return readNumber() // Read it :)
                } else {
                    return readSymbol() // Else read the symbol ^^
                }
            } else match(next) {
                    case ')' => MisplacedParenthesisError new(line()) throw()
                    case '(' => return readList() // hey, a parenthesis! it must be a list :)
                    case '\\' => return readCharacter() // hey, a backslash! it must be a character ;) 
                    case '"' => return readString() // hey, double quotes! it must be a string :) "
                    case '\'' => return readQuote() // hey, a quote! it must be the quote special character ^^
                    case ';' => skipLine() 
                                return null // Hey, its comment =D
                    case '`' => return readBackQuote() // A backquote!
                    case ',' => return readComma() // A comma!
                    case => return readSymbol() // well, all the rest are symbols :p
            }
            
        }
        null
    }
    
    readString : func -> Value {
        currIndex += 1
        if(currIndex >= text size) {
            MalformedLiteralError new("string","\"",line()) throw()
        }
    
        str := readTill('"') // "
    
        if(text[currIndex] != '"') { // Check we hit the closing quotes before the end of file "
            MalformedLiteralError new("string","\"",line()) throw()
        }
        currIndex += 1
    
        lString new(EscapeSequence unescape(str),line()) as Value
    }
    
    readCharacter : func -> Value {
        currIndex += 1
        if(currIndex >= text size) {
            MalformedLiteralError new("character",line()) throw()
        }
    
        c := text[currIndex]
        currIndex += 1
    
        Character new(c,line()) as Value
        
    }
    
    readNumber : func -> Value {
        str := readTillWhite()
        negative? := false
        if(str[0] == '-') {
            str = str[1..-1]
            negative? = true
        }
        if(str contains?('.')) {
            dot? := false
            for(c in str) {
                if(!c digit?() && c != '.') { // Check that all of the strings characters are digits or dots (for Float)
                    InvalidDoubleError new(str,line()) throw()
                } else if(c == '.' && !dot?) {  // And that there is only one dot in there :P
                    dot? = true 
                } else if(c == '.' && dot?) {
                    InvalidDoubleError new(str,line()) throw()
                }
            }
            n := ((negative?) ? -1 * (str toDouble()) : str toDouble()) as Double
            return Number new(n,line())
        }

        for(c in str) {
            if(!c digit?()) {
                InvalidIntegerError new(str,line()) throw() // In Ints, there are only digits :o
            }
        }
        n := (negative?) ? -1 * (str toInt()) : str toInt()
        Number new(n,line())
    }
    
    readList : func -> Value {
        line := line()
        currIndex += 1
        if(currIndex >= text size) {
            MalformedLiteralError new("list",")",line) throw()
        }
    
        skipWhite()
        
        if(currIndex >= text size) {
            MalformedLiteralError new("list",")",line) throw()
        }
    
        if(text[currIndex] == ')') {
            currIndex += 1
            return vList new() as Value
        }
    
        first := read()

        skipWhite()
        
        //TODO: add dotted (improper) list support
    
        list := ArrayList<Value> new()
        list add(first)
        while(true) {
            if(currIndex+1 >= text size && text[currIndex] != ')') {
                MalformedLiteralError new("list",")",line()) throw()
            } else {
                skipWhite()
                if(currIndex >= text size) {
                    MalformedLiteralError new("list",")",line()) throw()
                }
            }
            if(text[currIndex] == ')') {
                currIndex += 1
                break
            }
            list add(read())
        }
        
        vList new(list,line) as Value
    }
    
    readSymbol : func -> Value {
        Symbol new(readTillWhite(),line())
    }
    
    readQuote : func -> Value {
        currIndex += 1
        if(currIndex >= text size) {
            MalformedLiteralError new("quote",line()) throw()
        }
    
        list := ArrayList<Value> new()
        list add(Symbol new("quote",line()))
        list add(read())
    
        vList new(list,line()) as Value
    }
    
    readBackQuote : func -> Value {
        currIndex += 1
        if(currIndex >= text size) {
            MalformedLiteralError new("backquote",line()) throw()
        }
    
        list := ArrayList<Value> new()
        list add(Symbol new("backquote",line()))
        list add(read())
    
        vList new(list,line()) as Value
    }
    
    readComma : func -> Value {
        currIndex += 1
        if(currIndex >= text size) {
            MalformedLiteralError new("comma",line()) throw()
        }
    
        list := ArrayList<Value> new()
    
        if(text[currIndex] == '@') {
            currIndex += 1
            if(currIndex >= text size) {
                MalformedLiteralError new("comma-at",line()) throw()
            }
        
            
            list add(Symbol new("comma-at",line()))
        } else {
            list add(Symbol new("comma",line()))
        }
    
        list add(read())
    
        vList new(list,line()) as Value
    
    }
    
    skipWhite : func {
        while(currIndex < text size && text[currIndex] whitespace?()) {
            currIndex += 1
        }
    }
    
    readTillWhite : func -> String {
        ret := ""
        while(currIndex < text size && !text[currIndex] whitespace?() && text[currIndex] != ')') {
            ret += text[currIndex]
            currIndex += 1
        }
        ret
    }
    
    readTill : func(c : Char) -> String {
        ret := ""
        while(currIndex < text size && text[currIndex] != c) {
            ret += text[currIndex]
            currIndex += 1
        }
        ret
    }
    
    
    skipLine : func {
        while(currIndex < text size && text[currIndex] != '\n' && text[currIndex] != '\r') {
            currIndex += 1
        }
        currIndex += 1
    
    }
    
    line : func -> UInt {
        lines : UInt = 1
        for(val in text[0 .. currIndex]) { // Iterate through all chars we have currently read
            if(val == '\n') { // If we hit '\n' it means we have a new line, wether on unix(-like) systems, where newline is '\n' or on windows systems where it is "\r\n"
                lines += 1
            }
        }
        lines
    }
}
